package com.quanz.ckgl.services.db;

import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Types;
import java.util.ArrayList;
import java.util.List;

import javax.annotation.Resource;

import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.AutoCloseInputStream;
import org.apache.commons.io.output.DeferredFileOutputStream;
import org.crank.crud.CriteriaUtils;
import org.crank.crud.criteria.Criterion;
import org.crank.crud.criteria.OrderBy;
import org.joda.time.DateTime;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.dao.DuplicateKeyException;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.support.AbstractLobCreatingPreparedStatementCallback;
import org.springframework.jdbc.support.lob.LobCreator;
import org.springframework.jdbc.support.lob.LobHandler;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import com.quanz.ckgl.services.ImageInfo;
import com.quanz.ckgl.services.Product;
import com.quanz.ckgl.services.ProductService;
import com.quanz.ckgl.services.dao.ProductDao;
import com.quanz.ckgl.services.dao.ProductImageInfoDao;
import com.quanz.ckgl.services.entity.ImageInfoEntity;
import com.quanz.ckgl.services.entity.ProductEntity;

/**
 * 基于数据库的{@link ProductService}实现
 * 
 * @author James Quan
 * @version 2015年10月10日 下午9:41:07
 */
@Component
public class DbProductService implements ProductService {
	private Logger logger = LoggerFactory.getLogger(getClass());
	private ProductDao productDao;
	private ProductImageInfoDao productImageInfoDao;
	private JdbcTemplate jdbcTemplate;
	private LobHandler lobHandler;

	public ProductDao getProductDao() {
		return productDao;
	}

	@Resource
	public void setProductDao(ProductDao productDao) {
		this.productDao = productDao;
	}

	public ProductImageInfoDao getProductImageInfoDao() {
		return productImageInfoDao;
	}

	@Resource
	public void setProductImageInfoDao(ProductImageInfoDao productImageInfoDao) {
		this.productImageInfoDao = productImageInfoDao;
	}

	public JdbcTemplate getJdbcTemplate() {
		return jdbcTemplate;
	}

	@Autowired
	public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
		this.jdbcTemplate = jdbcTemplate;
	}

	public LobHandler getLobHandler() {
		return lobHandler;
	}

	@Autowired
	public void setLobHandler(LobHandler lobHandler) {
		this.lobHandler = lobHandler;
	}

	@Override
	@Transactional(readOnly = true)
	public Product findProductById(String id) {
		ProductEntity entity = productDao.findOne(id);
		return wrap(entity);
	}
	
	@Override
	@Transactional(readOnly = true)
	public Product findProductByBarcode(String barcode) {
		ProductEntity entity = productDao.findByBarcode(barcode);
		return wrap(entity);
	}
	
	@Override
	@Transactional(readOnly = true)
	public Product[] findProduct(OrderBy[] orderBy, int startPosition,
			int maxResults, Criterion... criteria) {
		Specification<ProductEntity> spec = CriteriaUtils.getSpecification(
				ProductEntity.class, criteria);
		Sort sort = CriteriaUtils.getSort(orderBy);
		List<ProductEntity> entities = productDao.findList(spec, startPosition,
				maxResults, sort);
		List<Product> products = new ArrayList<Product>(entities.size());
		for (ProductEntity entity : entities) {
			Product product = wrap(entity);
			products.add(product);
		}
		return products.toArray(new Product[products.size()]);
	}

	@Override
	@Transactional(readOnly = true)
	public int findProductCount(Criterion... criteria) {
		Specification<ProductEntity> spec = CriteriaUtils.getSpecification(
				ProductEntity.class, criteria);
		long c = productDao.count(spec);
		return (int) c;
	}

	@Override
	@Transactional(readOnly = true)
	public ImageInfo[] findProductImages(String id) {
		List<ImageInfoEntity> list = productImageInfoDao.findByProductId(id);
		List<ImageInfo> infos = new ArrayList<ImageInfo>(list.size());
		for (ImageInfoEntity entity : list) {
			infos.add(wrap(entity));
		}
		return infos.toArray(new ImageInfo[infos.size()]);
	}

	@Override
	public ImageInfo findProductImageByName(String productId, String name) {
		ImageInfoEntity entity = productImageInfoDao.findByProductIdAndName(
				productId, name);
		return wrap(entity);
	}

	@Override
	@Transactional(readOnly = true)
	public InputStream getProductImage(long id) throws IOException {
		ImageInfoEntity entity = productImageInfoDao.findOne(id);
		if (entity == null) {
			return null;
		}
		String sql = "SELECT DATA FROM PRODUCT_IMAGES WHERE ID = ?";
		DeferredFileOutputStream out = jdbcTemplate.queryForObject(sql,
				new Object[] { id }, new int[] { Types.BIGINT },
				new LobRowMapper(id));
		if (out == null) {
			return null;
		}
		if (out.isInMemory()) {
			return new ByteArrayInputStream(out.getData());
		} else {
			InputStream in = new AutoCloseInputStream(new FileInputStream(
					out.getFile())) {
				private boolean closed = false;

				@Override
				public void close() throws IOException {
					super.close();
					if (!closed) {
						closed = true;
						out.getFile().delete();
					}
				}
			};
			return in;
		}
	}

	@Override
	@Transactional
	public Product createProduct(Product product) {
		if (findProductById(product.getId()) != null) {
			throw new DuplicateKeyException("product " + product.getId()
					+ " already existed!");
		}
		ProductEntity entity = new ProductEntity();
		entity.setId(product.getId());
		entity.setName(product.getName());
		entity.setVendor(product.getVendor());
		entity.setPhone(product.getPhone());
		entity.setCatalog(product.getCatalog());
		entity.setColor(product.getColor());
		entity.setUnit(product.getUnit());
		entity.setSpec(product.getSpec());
		entity.setBarcode(product.getBarcode());
		productDao.persist(entity);
		return wrap(entity);
	}

	@Override
	@Transactional
	public void updateProduct(Product product) {
		ProductEntity entity = productDao.findOne(product.getId());
		if (entity == null) {
			throw new IllegalArgumentException("product " + product.getId()
					+ " not found!");
		}
		entity.setName(product.getName());
		entity.setVendor(product.getVendor());
		entity.setPhone(product.getPhone());
		entity.setCatalog(product.getCatalog());
		entity.setColor(product.getColor());
		entity.setUnit(product.getUnit());
		entity.setSpec(product.getSpec());
		entity.setBarcode(product.getBarcode());
		entity = productDao.merge(entity);
	}

	@Override
	@Transactional
	public void removeProduct(Product product) {
		ProductEntity entity = productDao.findOne(product.getId());
		if (entity == null) {
			return;
		}
		productDao.delete(entity);
	}

	@Override
	public ImageInfo createImage(ImageInfo info, InputStream data) {
		ImageInfoEntity entity = new ImageInfoEntity();
		entity.setName(info.getName());
		entity.setProductId(info.getProductId());
		entity.setSize(info.getSize());
		entity.setMimeType(info.getMimeType());
		entity.setLastModified(info.getLastModified() == null ? DateTime.now()
				: info.getLastModified());
		productImageInfoDao.persistAndFlush(entity);
		jdbcTemplate.execute("UPDATE PRODUCT_IMAGES SET DATA = ? WHERE ID = ?",
				new AbstractLobCreatingPreparedStatementCallback(lobHandler) {
					protected void setValues(PreparedStatement ps,
							LobCreator lobCreator) throws SQLException {
						lobCreator.setBlobAsBinaryStream(ps, 1, data,
								(int)info.getSize());
						ps.setLong(2, entity.getId());
					}
				});
		return wrap(entity);
	}

	@Override
	@Transactional
	public void removeImage(ImageInfo info) {
		ImageInfoEntity entity = productImageInfoDao.findOne(info.getId());
		if (entity == null) {
			return;
		}
		productImageInfoDao.delete(entity);
	}

	protected Product wrap(ProductEntity entity) {
		if (entity == null) {
			return null;
		}
		Product product = new Product();
		product.setId(entity.getId());
		product.setName(entity.getName());
		product.setVendor(entity.getVendor());
		product.setPhone(entity.getPhone());
		product.setUnit(entity.getUnit());
		product.setSpec(entity.getSpec());
		product.setBarcode(entity.getBarcode());
		product.setCatalog(entity.getCatalog());
		product.setColor(entity.getColor());
		return product;
	}

	protected ImageInfo wrap(ImageInfoEntity entity) {
		if (entity == null) {
			return null;
		}
		ImageInfo info = new ImageInfo();
		info.setId(entity.getId());
		info.setProductId(entity.getProductId());
		info.setName(entity.getName());
		info.setSize(entity.getSize());
		info.setMimeType(entity.getMimeType());
		info.setLastModified(entity.getLastModified());
		return info;
	}

	protected class LobRowMapper implements RowMapper<DeferredFileOutputStream> {
		private long id;

		public LobRowMapper(long id) {
			super();
			this.id = id;
		}

		@Override
		public DeferredFileOutputStream mapRow(ResultSet rs, int rowNum)
				throws SQLException {
			InputStream in = lobHandler.getBlobAsBinaryStream(rs, "DATA");
			if (in == null) {
				return null;
			}
			try {
				DeferredFileOutputStream out = new DeferredFileOutputStream(
						1024 * 32, "tmp", ".dat", null);
				try {
					IOUtils.copy(in, out);
					out.close();
					return out;
				} catch (IOException e) {
					logger.warn("read image " + id + " error!", e);
					return null;
				}
			} finally {
				IOUtils.closeQuietly(in);
			}
		}
	}
}
